chance = new require('chance')()

log = require '../log'
util = require '../util'
config = require '../config'
error = require '../error'

Meta = require '../meta/Meta'

EntityService = require './EntityService'
EntityServiceCache = require './EntityServiceCache'

userCache = {}
roleCache = {}

EntityServiceCache.onUpdatedOrRemoved (ctx, entityName, ids)->
    if entityName == 'F_User'
        for id in ids
            delete userCache[id]
    else if entityName == 'F_UserRole'
        for id in ids
            delete roleCache[id]

exports.gInit = ->
    mongo = require '../storage/Mongo'
    db = yield mongo.pDatabase()
    c = db.collection(Meta.getEntityMeta('F_User').tableName)
    userCount = yield c.count({})
    if userCount == 0
        log.system.info 'Create default admin user!!!'
        yield c.insertOne({
            _id: Meta.newObjectId().toString(), admin: true
            username: 'admin', password: Meta.hashPassword('admin'),
        })

exports.gUserById = (id)->
    user = userCache[id]
    return user if user

    user = yield from EntityService.gFindOneByCriteria({}, null, "F_User", {_id: id})
    if user
        userCache[id] = user
        user.subAppAccessible = util.arrayToTrueObject(user.subAppAccessible)
        user.urlPermissions = util.arrayToTrueObject(user.urlPermissions)
        user.entityPermissions = util.arrayToTrueObject(user.entityPermissions)
        user.menuPermissions = util.arrayToTrueObject(user.menuPermissions)
    user

exports.gRoleById = (id)->
    role = roleCache[id]
    return role if role

    role = yield from EntityService.gFindOneByCriteria({}, null, "F_UserRole", {_id: id})
    if role
        roleCache[id] = role
        role.subAppAccessible = util.arrayToTrueObject(role.subAppAccessible)
        role.urlPermissions = util.arrayToTrueObject(role.urlPermissions)
        role.entityPermissions = util.arrayToTrueObject(role.entityPermissions)
        role.menuPermissions = util.arrayToTrueObject(role.menuPermissions)
    role

exports.gAuthToken = (subApp, userId, userToken)->
    session = yield from EntityService.gFindOneByCriteria({}, null, 'F_UserSession', {subApp, userId})
    unless session
        log.debug 'no such session', subApp, userId
        return false

    if session.userToken != userToken
        log.debug 'token not match', {subApp, userId, userToken, sessionUserToken: session.userToken}
        return false

    if session.expireAt < Date.now()
        log.debug 'token expired', {subApp, userId, expireAt: session.expireAt}
        return false

    yield from exports.gUserById(userId)

# 登录
# TODO 思考：如果用户之前登录此子应用的 session 未过期，是返回之前的 session 还是替换 session
exports.gSignIn = (subAppName, username, password)->
    throw new error.UserError("PasswordNotMatch") unless password

    subApp = yield from EntityService.gFindOneByCriteria({}, null, 'F_SubApp', {name: subAppName})
    throw new error.UserError("SubAppNotExisted") unless subApp?

    usernameFields = subApp.usernameFields
    unless usernameFields && usernameFields.length
        usernameFields = ["username", "phone", "email"]

    matchFields = ({field: f, operator: "==", value: username} for f in usernameFields)
    criteria = {relation: 'or', items: matchFields}

    user = yield from EntityService.gFindOneByCriteria({}, null, "F_User", criteria)

    throw new error.UserError("UserNotExisted") unless user?
    throw new error.UserError("UserDisabled") if user.disabled
    throw new error.UserError("PasswordNotMatch") if Meta.hashPassword(password) != user.password

    session = {}
    session.subApp = subAppName
    session.userId = user._id
    session.userToken = chance.string(length: 20)
    session.expireAt = Date.now() + config.sessionExpireAtServer

    yield from exports.gSignOut(subAppName, user._id) # 先退出
    yield from EntityService.gCreate({}, "F_UserSession", session)

    session

# 登出特定子应用
exports.gSignOut = (subAppName, userId)->
    criteria = {userId: userId, subApp: subAppName}
    yield from EntityService.gRemoveManyByCriteria({}, 'F_UserSession', criteria)

# 添加用户（核心信息）
exports.gAddUser = (userInput)->
    user =
        _id: Meta.newObjectId().toString() # 用户 ID 同一直接用字符串
        username: userInput.username, password: userInput.password
        phone: userInput.phone, email: userInput.email

    yield from EntityService.gCreate({}, "F_User", user)

# 修改绑定的手机
exports.gChangePhone = (userId, phone)->
    user = yield from EntityService.gFindOneByCriteria({}, null, "F_User", {_id: userId})
    throw new error.UserError("UserNotExisted") unless user?
    throw new error.UserError("UserDisabled") if user.disabled

    idVersions = [{_id: userId, _version: user._version}]
    yield from EntityService.gUpdateMany({}, 'F_User', idVersions, {phone: phone})

    delete userCache[userId]

# 修改绑定的邮箱
exports.gChangeEmail = (userId, email)->
    user = yield from EntityService.gFindOneByCriteria({}, null, "F_User", {_id: userId})
    throw new error.UserError("UserNotExisted") unless user?
    throw new error.UserError("UserDisabled") if user.disabled

    idVersions = [{_id: userId, _version: user._version}]
    yield from EntityService.gUpdateMany({}, 'F_User', idVersions, {email: email})

    delete userCache[userId]

# 修改密码
exports.gChangePassword = (userId, oldPassword, newPassword)->
    user = yield from EntityService.gFindOneByCriteria({}, null, "F_User", {_id: userId})
    throw new error.UserError("UserNotExisted") unless user?
    throw new error.UserError("UserDisabled") if user.disabled
    throw new error.UserError("PasswordNotMatch") if Meta.hashPassword(oldPassword) != user.password

    idVersions = [{_id: userId, _version: user._version}]
    yield from EntityService.gUpdateMany({}, 'F_User', idVersions, {password: Meta.hashPassword(newPassword)})

    yield from gRemoveAllUserSessionOfUser user._id

# 通过手机重置密码
exports.gResetPasswordByPhone = (phone, password)->
    user = yield from EntityService.gFindOneByCriteria({}, null, "F_User", {phone: phone})
    throw new error.UserError("UserNotExisted") unless user?
    throw new error.UserError("UserDisabled") if user.disabled

    idVersions = [{_id: user._id, _version: user._version}]
    yield from EntityService.gUpdateMany({}, 'F_User', idVersions, {password: Meta.hashPassword(password)})

    yield from gRemoveAllUserSessionOfUser user._id

# 通过邮箱重置密码
exports.gResetPasswordByEmail = (email, password)->
    user = yield from EntityService.gFindOneByCriteria({}, null, "F_User", {email: email})
    throw new error.UserError("UserNotExisted") unless user?
    throw new error.UserError("UserDisabled") if user.disabled

    idVersions = [{_id: user._id, _version: user._version}]
    yield from EntityService.gUpdateMany({}, 'F_User', idVersions, {password: Meta.hashPassword(password)})

    yield from gRemoveAllUserSessionOfUser user._id

gRemoveAllUserSessionOfUser = (userId) ->
    yield from EntityService.gRemoveManyByCriteria({}, 'F_UserSession', {useId: userId})

exports.gCreateUser = (user)->
    user._id = Meta.newObjectId().toString()
    user.password = Meta.hashPassword(user.password)
    r = yield from EntityService.gCreate({}, "F_User", user)
    r._id

